package com.le.tester.sevice.impl;

import com.le.tester.dao.StockDao;
import com.le.tester.dto.GoodsStockDto;
import com.le.tester.lock.AbstractLock;
import com.le.tester.lock.RedisLockSimple;
import com.le.tester.sevice.StockService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 描述：评论的实现层：reduceStockOne，reduceStockForUpdate都是Mysql悲观锁，一上来就上锁了
 * 乐观锁
 * 由此可见，数据库的锁加上jvm的锁才能实现分布式的扣减库存失败
 */
//当synchronized锁住当前的bean 遇上@scope value=prototype proxyMode = ScopedProxyMode.TARGET_CLASS，懒汉加载模式，每次请求都会给一个新的实例
//锁失效了
//@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class StockServiceImpl implements StockService {

    @Resource
    private StockDao stockDao;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

//    @Autowired
//    private CuratorFramework curatorFramework;

    //当方法中的数据是会因为执行的时候变化，那么在高并发的时候就需要进行同步

    //当synchronized锁住当前的bean 遇上@Transactional，事务开启，整个方法被事务托管，第一个线程执行然后释放锁，事务并没有提交，
    // 第二个线程进来拿到了锁，执行的业务逻辑的方法拿到的数据是第一个线程事务未提交之前的数据，所以锁失效了
    //解决，把锁的层级提高
    //@Transactional
    @Override
    public synchronized String reduceStock(Long goodsId, Integer count) {


        long remainStock = stockDao.selectStockByGoodsId(goodsId);
        if (remainStock < count) {
            return "库存不足";
        }
        long reduceCount = remainStock - count;
        stockDao.updateStockByGoodsId(reduceCount, goodsId);
        return "库存扣减成功";

    }

    //分布式的时候，当前库存量需要从数据库查出剩余的库存量，
    //就考虑一条sql来解决，但是sql会包含逻辑计算(这么做的话，要是事务没有提交，会锁表，其他的sql语句需要等事务提交之后才能执行)，sql会有行锁
    //而且当你要更新goodsId = 1的时候，goodsId = 2的也会被锁住，其他的也会
    //可以在where后面的条件字段增加索引，减少锁影响的范围，所以这个方案还是不要用
    //这种就是减少数据的可执行空间
    @Override
    public String reduceStockOne(Long goodsId, Integer count) {
        int result = stockDao.updateStockByGoodsIdOne(goodsId, count);
        if (result > 0) {
            return "库存扣减成功";
        }
        return "库存不足";

    }

    @Transactional
    @Override
    public synchronized String reduceStockForUpdate(Long goodsId, Integer count) {

        //那我就加查询语句锁住表
        //没有加上事务的时候，查询出来的数据依然不能实时的提交
        //这边是对可变数据进行加锁
        //这种for update 也是锁表，然后性能极差，容易造成死锁，这种丢弃
        long remainStock = stockDao.selectStockByGoodsIdForUpdate(goodsId);
        if (remainStock < count) {
            return "库存不足";
        }
        long reduceCount = remainStock - count;
        stockDao.updateStockByGoodsId(reduceCount, goodsId);
        return "库存扣减成功";
    }


    //数据库增加版本号，来达到最终的一致性，会一直在执行，直到这次扣减成功，读多写少比较合适，一直重试写数据，也是很耗性能的
    @Override
    public synchronized String reduceStockCAS(Long goodsId, Integer count) {


        int result = 0;
        while (result == 0) {
            List<GoodsStockDto> goodsStockInfo = stockDao.selectStockInfoByGoodsId(goodsId);
            if (CollectionUtils.isEmpty(goodsStockInfo)) {
                return "商品不存在";
            }
            GoodsStockDto goodsStock = goodsStockInfo.get(0);
            Long remainStock = goodsStock.getStock();
            if (remainStock < count) {
                return "库存不足";
            }
            Integer version = goodsStock.getVersion();
            long reduceCount = remainStock - count;
            //sql里面都是version+1，就不会出现ABA的问题。如果同时出现+1，-1，那就要特别注意ABA问题
            result = stockDao.updateStockByGoodsIdCAS(reduceCount, goodsId, version);
        }
        return "库存扣减成功";

    }

    //扣减库存和查找库存都是从redis中获取，数据并不实时，不要使用
    @Override
    public String reduceStockRedisLock(Long goodsId, Integer count) {
        AbstractLock lock = null;
        try {
            lock = new RedisLockSimple(redisTemplate, "lock:" + goodsId, UUID.randomUUID().toString());
            //lock.lock();
            //lockDuplicate(lock);
            boolean tryLock = lock.tryLock(2000, TimeUnit.MILLISECONDS);
            if (!tryLock) {
                System.out.println("获取锁超时");
                return "系统繁忙";
            }
            String stockGoodIdKey = "stock:" + goodsId;
            boolean exist = redisTemplate.hasKey(stockGoodIdKey);
            if (!exist) {
                return "商品不存在";
            }
            long remainStock = Long.parseLong(String.valueOf(redisTemplate.opsForValue().get(stockGoodIdKey)));
            if (remainStock < count) {
                return "库存不足";
            }
            redisTemplate.opsForValue().set(stockGoodIdKey, (remainStock - count));
            return "库存扣减成功";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (Objects.nonNull(lock)) {
                lock.unlock();
//                System.out.println("锁已经释放");
            }
        }
    }

    //二次加锁
    private void lockDuplicate(AbstractLock lock) {
        lock.lock();
        lock.unlock();

    }

    //redisson
    public String reduceStockRedissonLock(Long goodsId, Integer count) {
        String lockId = "lock:" + goodsId;
        String stockGoodIdKey = "stock:" + goodsId;
        RLock lock = null;
        try {
            lock = redissonClient.getLock(lockId);
            lock.lock();
            boolean exist = redisTemplate.hasKey(stockGoodIdKey);
            if (!exist) {
                return "商品不存在";
            }
            long remainStock = Long.parseLong(String.valueOf(redisTemplate.opsForValue().get(stockGoodIdKey)));
            if (remainStock < count) {
                return "库存不足";
            }
            redisTemplate.opsForValue().set(stockGoodIdKey, (remainStock - count));
            return "库存扣减成功";
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (Objects.nonNull(lock)) {
                lock.unlock();
//                System.out.println("锁已经释放");
            }
        }
    }

    /**
     //curator framework
     public String reduceStockCurator(Long goodsId, Integer count) throws Exception {
     InterProcessMutex lock = null;
     String lockPath = "/" + "lock" + goodsId;
     String stockGoodIdKey = "stock:" + goodsId;
     try {
     lock = new InterProcessMutex(curatorFramework, lockPath);
     lock.acquire();
     boolean exist = redisTemplate.hasKey(stockGoodIdKey);
     if (!exist) {
     return "商品不存在";
     }
     long remainStock = Long.parseLong(String.valueOf(redisTemplate.opsForValue().get(stockGoodIdKey)));
     if (remainStock < count) {
     return "库存不足";
     }
     redisTemplate.opsForValue().set(stockGoodIdKey, (remainStock - count));
     return "库存扣减成功";
     } catch (Exception e) {
     throw new RuntimeException(e);
     } finally {
     if (Objects.nonNull(lock))
     lock.release();
     }
     }*/
}
